package com.htfyun.pdfview;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.pdf.PdfRenderer;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.graphics.Matrix;


import com.htfyun.Constants;
import com.htfyun.database.DaoSession;
import com.htfyun.database.GreenDaoManager;
import com.htfyun.database.PdfNote;
import com.htfyun.database.PdfNoteDao;
import com.htfyun.database.PngName;
import com.htfyun.database.PngNameDao;
import com.htfyun.utils.DebugLog;

import org.geometerplus.android.fbreader.covers.CoverManager;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import uk.co.senab.photoview.PhotoViewAttacher;

/**
 * Created by sst on 2018/1/18.
 */

public class PdfViewManager implements PhotoViewAttacher.OnMatrixChangedListener,
        PhotoViewAttacher.OnScaleChangeListener {

    protected static final int FIRST_PAGE = 0;
    protected static final float DEFAULT_QUALITY = 2.0f;
    protected static final int DEFAULT_OFFSCREENSIZE = 3;

    private String pdfPath;
    private PdfRenderer renderer;
    private BitmapContainer bitmapContainer;
    private ImageView imageView;
    private Context context;

    private boolean showNote = true;

    PhotoViewAttacher attacher;
    PdfScale scale = new PdfScale();

    int mCurPosition;

    private SparseArray<Bitmap> noteBitmapCache = (new SparseArray<Bitmap>());


    private static class MinPriorityThreadFactory implements ThreadFactory {
        private final ThreadFactory myDefaultThreadFactory = Executors.defaultThreadFactory();

        public Thread newThread(Runnable r) {
            final Thread th = myDefaultThreadFactory.newThread(r);
            th.setPriority(Thread.MIN_PRIORITY);
            return th;
        }
    }

    private final ExecutorService myPool = Executors.newFixedThreadPool(1, new MinPriorityThreadFactory());


    public PdfViewManager(Context context, ImageView imageView, String pdfPath) {
        this.context = context;
        this.imageView = imageView;
        this.pdfPath = pdfPath;

        showNote = true;

        mCurPosition = FIRST_PAGE;

        try {
            renderer = new PdfRenderer(getSeekableFileDescriptor(pdfPath));
            initBitmapContainerAndRenderInImageView(renderer, DEFAULT_QUALITY, mCurPosition);
        } catch (IOException e) {
            e.printStackTrace();
        }

        initAttacher(this.imageView);

    }

    public PdfViewManager(Context context, ImageView imageView, String pdfPath, int initPage) {
        this.context = context;
        this.imageView = imageView;
        this.pdfPath = pdfPath;

        showNote = true;

        mCurPosition = initPage;

        try {
            renderer = new PdfRenderer(getSeekableFileDescriptor(pdfPath));
            initBitmapContainerAndRenderInImageView(renderer, DEFAULT_QUALITY, mCurPosition);

        } catch (IOException e) {
            e.printStackTrace();
        }

        initAttacher(this.imageView);
    }

    private void initAttacher(ImageView imageView) {
        attacher = new PhotoViewAttacher(imageView);

        attacher.setZoomTransitionDuration(1);
        attacher.setScale(scale.getScale(), scale.getCenterX(), scale.getCenterY(), false);
        attacher.setOnMatrixChangeListener(this);

        attacher.setOnScaleChangeListener(this);

        attacher.update();
    }


    protected ParcelFileDescriptor getSeekableFileDescriptor(String path) throws IOException {
        ParcelFileDescriptor parcelFileDescriptor;

        File pdfCopy = new File(path);

        if (pdfCopy.exists()) {
            parcelFileDescriptor = ParcelFileDescriptor.open(pdfCopy, ParcelFileDescriptor.MODE_READ_ONLY);
            return parcelFileDescriptor;
        }

        if (isAnAsset(path)) {
            pdfCopy = new File(context.getCacheDir(), path);
            parcelFileDescriptor = ParcelFileDescriptor.open(pdfCopy, ParcelFileDescriptor.MODE_READ_ONLY);
        } else {
            URI uri = URI.create(String.format("file://%s", path));
            parcelFileDescriptor = context.getContentResolver().openFileDescriptor(Uri.parse(uri.toString()), "rw");
        }

        return parcelFileDescriptor;
    }

    private boolean isAnAsset(String path) {
        return !path.startsWith("/");
    }

    private void initBitmapContainerAndRenderInImageView(PdfRenderer renderer, float renderQuality, int position) {
        PdfRenderer.Page samplePage = getPDFPage(renderer, position);

        PdfRendererParams params = new PdfRendererParams();

        params.setRenderQuality(renderQuality);
        params.setOffScreenSize(DEFAULT_OFFSCREENSIZE);
        params.setWidth((int) (samplePage.getWidth() * renderQuality));
        params.setHeight((int) (samplePage.getHeight() * renderQuality));

        DebugLog.e("params.getWidth = " + params.getWidth() + ", params.getHeight() = " + params.getHeight());

        bitmapContainer = new SimpleBitmapPool(params);

        Bitmap bitmap = bitmapContainer.get(position);
        samplePage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);

        bitmapContainer.setRenderBitmap(position, bitmap);
        imageView.setImageBitmap(mergeStoredPngWithPdfBitmap(bitmap));

        samplePage.close();

    }

    protected PdfRenderer.Page getPDFPage(PdfRenderer renderer, int position) {
        return renderer.openPage(position);
    }

    public void close() {
        releaseAllBitmaps();
        if (renderer != null) {
            renderer.close();
        }
    }

    protected void releaseAllBitmaps() {
        if (bitmapContainer != null) {
            bitmapContainer.clear();
        }
        if (noteBitmapCache.size() > 0) {
            for(int i = 0, nsize = noteBitmapCache.size(); i < nsize; i++) {
                Bitmap bitmap = noteBitmapCache.valueAt(i);
                if (bitmap != null) {
                    bitmap.recycle();
                }
            }
            noteBitmapCache.clear();
        }
    }

    public int getCount() {
        return renderer != null ? renderer.getPageCount() : 0;
    }
    public String getPdfPath() {
        return pdfPath;
    }

    public boolean isShowNote() {
        return showNote;
    }

    public void setShowNote(boolean showNote) {
        this.showNote = showNote;
        setCurPosition(getCurrentPosition());
    }

    public int getCurrentPosition() {
        return mCurPosition;
    }


    public void setCurPosition(int position) {
        this.mCurPosition = position;

        if (renderer == null || getCount() < position) {
            return;
        }

        DebugLog.e("!!!!! position = " + position);
        long timeBefore = SystemClock.elapsedRealtime();

        PdfRenderer.Page page = getPDFPage(renderer, position);

        Bitmap bitmap = bitmapContainer.get(position);
        page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
        page.close();

        bitmapContainer.setRenderBitmap(position, bitmap);

        imageView.setImageBitmap(mergeStoredPngWithPdfBitmap(bitmap));

        imageView.forceEinkFullUpdate();

        DebugLog.e("decode one page time = " + (SystemClock.elapsedRealtime() - timeBefore));

    }

    private Bitmap mergeStoredPngWithPdfBitmap(Bitmap pdfBitmap) {

        if (!showNote) {
            return pdfBitmap;
        }

        Bitmap noteBmp = noteBitmapCache.get(getCurrentPosition());
        if (noteBmp == null) {
            noteBmp = getBmpFromPng();
            if (noteBmp != null) {
                noteBitmapCache.put(getCurrentPosition(), noteBmp);
            }
        }

        if (noteBmp == null) {
            return pdfBitmap;
        }

        return mergeBitmap(pdfBitmap, noteBmp);

    }


    private Bitmap getBmpFromPng() {

        DaoSession daoSession = GreenDaoManager.INSTANCE.getDaoSession();

        PdfNoteDao pdfNoteDao = daoSession.getPdfNoteDao();
        List<PdfNote> pdfNotes = pdfNoteDao.queryBuilder()
                .where(PdfNoteDao.Properties.PdfPath.eq(getPdfPath()), PdfNoteDao.Properties.PdfPagePosition.eq(getCurrentPosition()))
                .orderAsc(PdfNoteDao.Properties.Date)
                .limit(1).list();

        if (pdfNotes == null || pdfNotes.isEmpty()) {
            return null;
        }
        PdfNote pdfNote = pdfNotes.get(0);
        PngNameDao pngNameDao = daoSession.getPngNameDao();
        List<PngName> pngNames = pngNameDao.queryBuilder()
                .where(PngNameDao.Properties.OtherUuid.eq(pdfNote.getUuid()))
                .orderAsc(PngNameDao.Properties.Date)
                .limit(1).list();

        if (pngNames == null || pngNames.isEmpty()) {
            return null;
        }

        PngName pngName = pngNames.get(0);

        return getBitmapFromPngPath(Constants.toPngPath(pngName.getPngName()));

    }

    private PngName getOrCreatePngName() {

        DaoSession daoSession = GreenDaoManager.INSTANCE.getDaoSession();

        PdfNoteDao pdfNoteDao = daoSession.getPdfNoteDao();
        List<PdfNote> pdfNotes = pdfNoteDao.queryBuilder()
                .where(PdfNoteDao.Properties.PdfPath.eq(getPdfPath()), PdfNoteDao.Properties.PdfPagePosition.eq(getCurrentPosition()))
                .orderAsc(PdfNoteDao.Properties.Date)
                .limit(1).list();

        PdfNote note;
        if (pdfNotes == null || pdfNotes.isEmpty()) {
            note = new PdfNote();
            note.setDate(new Date());
            note.setUuid(UUID.randomUUID().toString());
            note.setPdfPagePosition(getCurrentPosition());
            note.setPdfPath(pdfPath);

            pdfNoteDao.insert(note);

        } else {
            note = pdfNotes.get(0);
        }

        PngNameDao pngNameDao = daoSession.getPngNameDao();
        List<PngName> pngNames = pngNameDao.queryBuilder()
                .where(PngNameDao.Properties.OtherUuid.eq(note.getUuid()))
                .orderAsc(PngNameDao.Properties.Date)
                .limit(1).list();

        PngName pngName;
        if (pngNames == null || pngNames.isEmpty()) {
            pngName = new PngName();

            pngName.setDate(new Date());
            pngName.setOtherUuid(note.getUuid());
            pngName.setUuid(UUID.randomUUID().toString());
            pngName.setPngName(pngName.getUuid() + ".png");

            pngNameDao.insert(pngName);

        } else {

            pngName = pngNames.get(0);

        }

        return pngName;

    }

    /**
     * 把两个位图覆盖合成为一个位图，以底层位图的长宽为基准
     *
     * @param backBitmap  在底部的位图
     * @param frontBitmap 盖在上面的位图
     * @return
     */
    public static Bitmap mergeBitmap(Bitmap backBitmap, Bitmap frontBitmap) {

        if (backBitmap == null || backBitmap.isRecycled()
                || frontBitmap == null || frontBitmap.isRecycled()) {
            return null;
        }
        Bitmap bitmap = backBitmap.copy(Bitmap.Config.ARGB_8888, true);
        Canvas canvas = new Canvas(bitmap);
        Rect baseRect = new Rect(0, 0, backBitmap.getWidth(), backBitmap.getHeight());
        Rect frontRect = new Rect(0, 0, frontBitmap.getWidth(), frontBitmap.getHeight());
        canvas.drawBitmap(frontBitmap, frontRect, baseRect, null);
        return bitmap;
    }

    private Bitmap getBitmapFromPngPath(String pngPath) {
        try {
            FileInputStream fis = new FileInputStream(pngPath);
            return BitmapFactory.decodeStream(fis);

        } catch (FileNotFoundException e) {

        }

        return null;
    }



    public void save(Bitmap bitmap) {

        if (bitmap == null || bitmap.isRecycled() || attacher == null) {
            return;
        }

        /////// 将要保存的 bitmap matrix 后 再和原来的图片合并在一起
        // 我们先把整个bmp放大到和当前放大参数后的尺寸, 然后再把新的bmp画上去后, 然后再缩小到和render一样的尺寸.

        Bitmap renderBitmap = bitmapContainer.getRenderBitmap(getCurrentPosition());
        Bitmap bmpNormal = Bitmap.createBitmap(renderBitmap.getWidth(), renderBitmap.getHeight(), renderBitmap.getConfig());
        bmpNormal.eraseColor(Color.TRANSPARENT);
        Bitmap bmpScale = Bitmap.createBitmap(bmpNormal, 0, 0, bmpNormal.getWidth(), bmpNormal.getHeight(),
                attacher.getDisplayMatrix(), true);
        Canvas canvas = new Canvas(bmpScale);
        RectF rectF = attacher.getDisplayRect();
        canvas.drawBitmap(bitmap, -rectF.left, -rectF.top, null);

        float widthScale = 1.0f * bmpNormal.getWidth() / bmpScale.getWidth();
        float heightScale = 1.0f * bmpNormal.getHeight() / bmpScale.getHeight();

        bmpNormal.recycle();
        bmpNormal = null;

        DebugLog.e("widthScale = " + widthScale + ", heightScale = " + heightScale);
        Matrix matrix = new Matrix();
        matrix.postScale(widthScale, heightScale); //长和宽放大缩小的比例
        bmpNormal = Bitmap.createBitmap(bmpScale, 0, 0, bmpScale.getWidth(), bmpScale.getHeight(), matrix, true);

        bmpScale.recycle();
        bmpScale = null;


//////////////////////////////////
        PngName pngName = getOrCreatePngName();
        if (pngName == null) {
            return;
        }

        Bitmap bitmapPng = getBitmapFromPngPath(Constants.toPngPath(pngName.getPngName()));


        Bitmap bmpNeedToSavePng = bmpNormal.copy(bmpNormal.getConfig(), true);
        if (bitmapPng != null) {
            bmpNeedToSavePng = mergeBitmap(bitmapPng, bmpNormal);
            bitmapPng.recycle();
            bitmapPng = null;
        }

        bmpNormal.recycle();
        bmpNormal = null;

        if (bmpNeedToSavePng != null) {

            noteBitmapCache.put(getCurrentPosition(), bmpNeedToSavePng);

            saveBitmap(bmpNeedToSavePng.copy(bmpNeedToSavePng.getConfig(), false), Constants.toPngPath(pngName.getPngName()));

        }

    }

    private void saveBitmap(Bitmap bitmap, String pngPath) {

        final Bitmap bmp = bitmap;
        final String path = pngPath;

        myPool.execute(new Runnable() {
            @Override
            public void run() {

                File file = new File(path);
                if (file.exists()) {
                    file.delete();
                }
                try {
                    FileOutputStream out = new FileOutputStream(file);
                    if (bmp.compress(Bitmap.CompressFormat.PNG, 100, out)) {
                        out.flush();
                        out.close();
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    public void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener onPhotoTapListener) {
        attacher.setOnPhotoTapListener(onPhotoTapListener);
    }

    public void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener) {
        attacher.setOnViewTapListener(listener);
    }

    @Override
    public void onMatrixChanged(RectF rect) {
//        DebugLog.e("scale.getScale = " + scale.getScale()+ ", attacher scale = " + attacher.getScale());
//        DebugLog.e("rect = " + rect);
    }

    @Override
    public void onScaleChange(float scaleFactor, float focusX, float focusY) {
//        DebugLog.e("scaleFactor = " + scaleFactor + ", focusX = " + focusX + ", focusY = " + focusY);
    }
}
